package org.teamswift.crow.rbac.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.teamswift.crow.rbac.common.ICrowUser;
import org.teamswift.crow.rbac.dto.CrowUserFromJWTPayloads;
import org.teamswift.crow.rbac.enums.CrowRbacErrors;
import org.teamswift.crow.rbac.exceptions.CredentialInvalidException;
import org.teamswift.crow.rest.result.CrowResultCode;
import org.teamswift.crow.rest.result.impl.CrowErrorResult;
import org.teamswift.crow.rest.utils.CrowBeanUtils;
import org.teamswift.crow.rest.utils.CrowMessageUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

@Component
@Getter
public class CrowSecurityUtils {

    @Value("${jwt.secretKey}")
    private String secretKey;

    @Value("${spring.application.name:crow}")
    private String issuer;

    private final String headerParam = "Authorization";

    @Value("${jwt.expireInMinutes:60L}")
    private final int expireInMinutes = 60;

    private final String tokenPrefix = "Bearer ";

    public String signByUser(ICrowUser<?> user) {

        Algorithm algorithm = Algorithm.HMAC256(secretKey);

        Map<String, ?> payloads = new HashMap<>(){{
            put("username", user.getUsername());
            put("name", user.getName());
            put("id", user.getId());
        }};

        Calendar date = Calendar.getInstance();
        date.add(Calendar.MINUTE, expireInMinutes);

        return JWT.create()
                .withIssuer(issuer)
                .withPayload(payloads)
                .withExpiresAt(date.getTime())
                .sign(algorithm);
    }

    public Map<String, Claim> verify(String token) throws JWTVerificationException {
        Algorithm algorithm = Algorithm.HMAC256(secretKey);
        JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer(issuer)
                .acceptExpiresAt(expireInMinutes * 60)
                .build();
        DecodedJWT jwt = verifier.verify(token);
        return jwt.getClaims();
    }

    public CrowUserFromJWTPayloads read(String token) {
        Map<String, Claim> raw;

        try {
             raw = verify(token);
        } catch (JWTVerificationException e) {
            throw new CredentialInvalidException(
                    CrowMessageUtil.error(CrowRbacErrors.TokenInvalid)
            );
        }

        return new CrowUserFromJWTPayloads(
                raw.get("id").asInt(),
                raw.get("username").asString(),
                raw.get("name").asString()
        );

    }

    static public void writeError(HttpServletRequest request, HttpServletResponse response, CrowResultCode resultCode, HttpStatus httpStatus, String message) {
        CrowErrorResult result = new CrowErrorResult();
        result.setResultCode(resultCode);
        result.setHttpStatusCode(httpStatus.value());
        result.setData(message);
        result.setSuccess(false);

        setHeader(request, response);
        response.setStatus(httpStatus.value());

        ObjectMapper objectMapper = CrowBeanUtils.getBean(ObjectMapper.class);
        try {
            response.getWriter().write(objectMapper.writeValueAsString(result));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static public void setHeader(HttpServletRequest request, HttpServletResponse response){
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", request.getMethod());
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));

        response.setHeader("Content-Type","application/json;charset=UTF-8");
        response.setStatus(HttpStatus.OK.value());
    }

}
